/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.chaos.view;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.Image;
import ohos.agp.components.TextField;
import ohos.agp.components.element.Element;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.media.image.PixelMap;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 密码输入框
 *
 * @since 2021-03-25
 */
public class PinView extends TextField implements Component.BindStateChangedListener {
    /**
     * 密码显示模式：隐藏密码,显示圆形
     */
    public static final int CONTENT_SHOW_MODE_PASSWORD = 1;
    /**
     * 密码显示模式：显示密码
     */
    public static final int CONTENT_SHOW_MODE_TEXT = 2;
    /**
     * 密码显示模式：隐藏密码,显示星号
     */
    public static final int CONTENT_SHOW_MODE_PASSWORD_STAR = 3;
    /**
     * 输入框相连的样式
     */
    public static final int INPUT_BOX_STYLE_CONNECT = 1;
    /**
     * 单个的输入框样式
     */
    public static final int INPUT_BOX_STYLE_SINGLE = 2;
    /**
     * 下划线输入框样式
     */
    public static final int INPUT_BOX_STYLE_UNDERLINE = 3;
    /**
     * 输入框是4
     */
    private static final int NUMBER_ITEM = 4;
    private static final int ZERO = 0;
    private static final int ONE = 1;
    private static final int TWO = 2;
    private static final int SIX = 6;
    private static final int TEN = 10;
    private static final int TWENTY = 20;
    private static final int ONE_HUNDRED = 100;
    private static final int ONE_HUNDRED_AND_TEN = 110;
    private static final int FIVE_HUNDRED = 500;

    // 画笔
    private RectFloat mRectFloatConnect;
    private RectFloat mRectFloatSingleBox;
    private Paint mPaintDivisionLine;
    private Paint mPaintContent;
    private Paint mPaintBorder;
    private Paint mPaintUnderline;
    private Paint mPaintHint;

    // 边框大小
    private float mOtpLineWidth;

    // 边框颜色
    private Color mOtpLineColorFocus;
    private Color mOtpLineColorNoFocus;

    // 圆角大小
    private float mCornerSize;

    // 分割线大小
    private float mDivisionLineSize;

    // 分割线颜色
    private Color mDivisionColor;

    // 圆形密码的半径大小
    private float mCircleRadius;

    // 密码框长度
    private int mOtpItemCount;

    // 密码显示模式
    private int mOptContentShowMode;

    // 单框和下划线输入样式下,每个输入框的间距
    private float mOtpItemSpacing;

    // 输入框样式
    private int mInputBoxStyle;

    // 1：风格为line1；2：风格为rectangle;
    private int mOtpViewType;

    // 字体大小
    private float mTextSize;

    // 字体颜色
    private Color mTextColor;

    // 每个输入框是否是正方形标识
    private boolean mIsInputBoxSquare;

    private boolean mIsonInputFinished = false;
    private Paint mPaintCursor;

    // 光标颜色
    private Color mOtpCursorColor;

    // 光标宽度
    private float mOtpCursorWidth;

    // 光标高度
    private int mCursorHeight;

    // 下划线输入样式下,输入框获取焦点时下划线颜色
    private Color mUnderlineFocusColor;

    // 下划线输入样式下,有输入时，下划线颜色
    private Color mUnderlineFullColor;

    // 下划线输入样式下,无输入时，下划线颜色
    private Color mUnderlineEmptyColor;

    // 有焦点时，item的背景色
    private Color mItemBackgroudFocus;

    // 没有焦点时，item的背景色
    private Color mItemBackgroudNoFocus;

    // 该控件没有焦点时的颜色
    private Color mBackgroudNoFocus;

    // item的背景图片
    private Element mItemBackgroudImg;

    // 底部文字
    private String mHintText;

    // 底部文字颜色
    private Color mHintColor;

    // item的宽高
    private int mItemHeight;
    private int mItemWidth;

    // 控件没有焦点时候，框的颜色
    private Color mBorderColorControlNoFocus;

    // 控件没有焦点时候，字体颜色
    private Color mTextColorControlNoFocus;

    private boolean mIsCursorFlag;

    private DrawTask mDrawTask = new DrawTask() {
        @Override
        public void onDraw(Component component, Canvas canvas) {
            // 绘制输入框
            switch (mInputBoxStyle) {
                case INPUT_BOX_STYLE_SINGLE:
                case INPUT_BOX_STYLE_UNDERLINE:
                case INPUT_BOX_STYLE_CONNECT:
                    drawBackgroundRect(canvas);
                    drawUnderlineStyle(canvas);
                    break;
                default:
                    drawConnectStyle(canvas);
                    break;
            }

            // 绘制输入框内容
            drawContent(canvas);

            // 绘制光标
            if (isFocused()) {
                drawCursor(canvas);
            } else {
                mIsCursorFlag = false;
                mPaintCursor.setAlpha(0f);
            }
        }
    };

    // 控制密码输满后重复回调onInputFinished（）,无法控制输入长度的替代方案
    private TextObserver mTextObserver = new TextObserver() {
        @Override
        public void onTextUpdated(String text, int i, int i1, int i2) {
            LogUtil.d("onTextUpdated text=" + text);
            LogUtil.d("onTextUpdated getText=" + getText());
            if (getText().length() < mOtpItemCount) {
                mIsonInputFinished = false;
            }
            LogUtil.d("onTextUpdated mIsonInputFinished=" + mIsonInputFinished);

            if (mOtpItemCount == NUMBER_ITEM || mOtpItemCount == 1) {
                if (!isNumeric(text)) {
                    setText(text.substring(0, text.length() - 1));
                }
            }

            // 设置光标画笔的alpha值
            invalidate();
        }
    };

    private EstimateSizeListener mEstimateSizeListener = new EstimateSizeListener() {
        @Override
        public boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {
            LogUtil.d("onEstimateSize-mInputBoxSquare=" + mIsInputBoxSquare);
            if (mIsInputBoxSquare) {
                int width = EstimateSpec.getSize(widthEstimateConfig);

                // 计算view高度,使view高度和每个item的宽度相等,确保每个item是一个正方形
                float itemWidth = getContentItemWidthOnMeasure(width);
                float itemHeight = itemWidth;

                if (mItemHeight != 0) {
                    width = DecimalUtils.addInt(DecimalUtils.add(DecimalUtils.mul(mItemWidth, mOtpItemCount),
                            DecimalUtils.mul(DecimalUtils.mul(mOtpLineWidth, mOtpItemCount), TWO)),
                            DecimalUtils.mul(DecimalUtils.div(1, mOtpItemCount), mOtpItemSpacing));
                    itemHeight = mItemHeight;
                }

                switch (mInputBoxStyle) {
                    case INPUT_BOX_STYLE_UNDERLINE:
                        setComponentSize(width, DecimalUtils.add(itemHeight, mOtpLineWidth));
                        break;
                    case INPUT_BOX_STYLE_SINGLE:
                    case INPUT_BOX_STYLE_CONNECT:
                    default:
                        setComponentSize(width, DecimalUtils.add(itemHeight, DecimalUtils.mul(mOtpLineWidth, TWO)));
                        break;
                }
            }
            return false;
        }
    };

    /**
     * 构造器
     *
     * @param context context
     * @param attrSet attrSet
     */
    public PinView(Context context, AttrSet attrSet) {
        this(context, attrSet, "");
    }

    /**
     * 构造器
     *
     * @param context
     * @param attrSet
     * @param styleName
     */
    public PinView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initAttrs(context, attrSet);
    }

    /**
     * 是否是数字
     *
     * @param str 传进来的文本
     * @return boolean
     */
    public static boolean isNumeric(String str) {
        Pattern pattern = Pattern.compile("[0-9]*");
        Matcher isNum = pattern.matcher(str);
        if (!isNum.matches()) {
            return false;
        }
        return true;
    }

    private void initAttrs(Context context, AttrSet attrs) {
        LogUtil.d("getHeight()=" + getHeight());
        mContext = context;
        mOtpLineWidth = AttrValue.getDimension(attrs, "OtpLineWidth", ONE);
        mOtpLineColorFocus = AttrValue.get(attrs, "OtpLineColorFocus", Color.TRANSPARENT);
        mOtpLineColorNoFocus = AttrValue.get(attrs, "OtpLineColorNoFocus", Color.TRANSPARENT);
        mCornerSize = AttrValue.getDimension(attrs, "corner_size", ZERO);
        mDivisionLineSize = AttrValue.getDimension(attrs, "divisionLineSize", ZERO);
        mDivisionColor = AttrValue.get(attrs, "divisionLineColor", Color.BLACK);
        mCircleRadius = AttrValue.get(attrs, "circleRadius", TWENTY);
        mOtpItemCount = AttrValue.get(attrs, "OtpItemCount", SIX);
        mOptContentShowMode = AttrValue.get(attrs, "OptcontentShowMode", CONTENT_SHOW_MODE_PASSWORD);
        mInputBoxStyle = AttrValue.get(attrs, "inputBoxStyle", INPUT_BOX_STYLE_CONNECT);
        mOtpViewType = AttrValue.get(attrs, "otpViewType", ONE);
        mOtpItemSpacing = AttrValue.getDimension(attrs, "OtpItemSpacing", TEN);
        mTextSize = getTextSize();
        LogUtil.d("text_size=" + mTextSize);
        mTextColor = getTextColor();
        setTextColor(Color.TRANSPARENT);
        mIsInputBoxSquare = AttrValue.get(attrs, "inputBoxSquare", true);
        mOtpCursorColor = AttrValue.get(attrs, "OtpCursorColor", Color.BLACK);
        mOtpCursorWidth = AttrValue.getDimension(attrs, "OtpCursorWidth", TWO);
        mCursorHeight = AttrValue.getDimension(attrs, "cursorHeight", ZERO);
        mUnderlineFullColor = AttrValue.get(attrs, "underlineFullColor", Color.BLACK);
        mUnderlineEmptyColor = AttrValue.get(attrs, "underlineEmptyColor", Color.BLACK);
        mUnderlineFocusColor = AttrValue.get(attrs, "underlineFocusColor", Color.BLACK);
        mItemBackgroudFocus = AttrValue.get(attrs, "itemBackgroudFocus", Color.TRANSPARENT);
        mItemBackgroudNoFocus = AttrValue.get(attrs, "itemBackgroudNoFocus", Color.TRANSPARENT);
        mBackgroudNoFocus = AttrValue.get(attrs, "backgroudNoFocus", Color.TRANSPARENT);
        if (attrs.getAttr("itemBackgroudImg").isPresent()) {
            mItemBackgroudImg = attrs.getAttr("itemBackgroudImg").get().getElement();
        }
        mHintText = AttrValue.get(attrs, "hintText", "");
        mHintColor = AttrValue.get(attrs, "hintColor", Color.GRAY);
        mItemHeight = AttrValue.getDimension(attrs, "itemHeight", 0);
        mItemWidth = AttrValue.getDimension(attrs, "itemWidth", 0);
        mBorderColorControlNoFocus = AttrValue.get(attrs, "borderColorControlNoFocus", Color.TRANSPARENT);
        mTextColorControlNoFocus = AttrValue.get(attrs, "textColorControlNoFocus", mTextColor);
        init();
        setEstimateSizeListener(mEstimateSizeListener);
        addDrawTask(mDrawTask);
        addTextObserver(mTextObserver);
        startCursorAnim();
    }

    private void init() {
        mPaintBorder = new Paint();
        mPaintBorder.setAntiAlias(true);
        mPaintBorder.setStyle(Paint.Style.FILL_STYLE);
        mPaintBorder.setStrokeWidth(mOtpLineWidth);
        mPaintBorder.setColor(mOtpLineColorNoFocus);

        mPaintDivisionLine = new Paint();
        mPaintDivisionLine.setAntiAlias(true);
        mPaintDivisionLine.setStyle(Paint.Style.STROKE_STYLE);
        mPaintDivisionLine.setStrokeWidth(mDivisionLineSize);
        mPaintDivisionLine.setColor(mDivisionColor);

        mPaintContent = new Paint();
        mPaintContent.setAntiAlias(true);
        mPaintContent.setTextSize((int) mTextSize);

        mPaintCursor = new Paint();
        mPaintCursor.setAntiAlias(true);
        mPaintCursor.setStrokeWidth(mOtpCursorWidth);
        mPaintCursor.setColor(mOtpCursorColor);

        mPaintUnderline = new Paint();
        mPaintUnderline.setAntiAlias(true);
        mPaintUnderline.setStrokeWidth(mOtpLineWidth);
        mPaintUnderline.setColor(mUnderlineEmptyColor);

        mPaintHint = new Paint();
        mPaintHint.setAntiAlias(true);
        mPaintHint.setTextSize((int) mTextSize);
        mPaintHint.setColor(mHintColor);

        setTextCursorVisible(false); // 隐藏原生光标

        // 避免onDraw里面重复创建RectF对象,先初始化RectF对象,在绘制时调用set()方法
        // 单个输入框样式的RectF
        mRectFloatSingleBox = new RectFloat();

        // 绘制Connect样式的矩形框
        mRectFloatConnect = new RectFloat();

        // 设置单行输入
        setMultipleLine(false);

        // 若构造方法中没有写成ohos.R.attr.editTextStyle的属性,应该需要设置该属性,EditText默认是获取焦点的
        setTouchFocusable(true);

        // 通过反射改变光标TextSelectHandle的样式
        setSelectionColor(Color.TRANSPARENT);

        // 限定输入字符数的替代方案
        setLayoutRefreshedListener(new LayoutRefreshedListener() {
            @Override
            public void onRefreshed(Component component) {
                if (getText().length() > mOtpItemCount) {
                    setText(getText().substring(0, mOtpItemCount));
                }
            }
        });
    }

    @Override
    public void onComponentBoundToWindow(Component component) {
    }

    @Override
    public void onComponentUnboundFromWindow(Component component) {
    }

    @Override
    public void estimateSize(int widthEstimatedConfig, int heightEstimatedConfig) {
        super.estimateSize(widthEstimatedConfig, heightEstimatedConfig);
    }

    /**
     * 根据输入内容显示模式,绘制内容是圆心还是明文的text
     *
     * @param canvas
     */
    private void drawContent(Canvas canvas) {
        int cy = getHeight() / TWO;
        String password = getText();
        if (mOptContentShowMode == CONTENT_SHOW_MODE_PASSWORD) {
            mPaintContent.setColor(mTextColor);
            for (int ii = 0; ii < password.length(); ii++) {
                float startX = getDrawContentStartX(ii);
                canvas.drawCircle(startX, cy, mCircleRadius, mPaintContent);
            }
        } else if (mOptContentShowMode == CONTENT_SHOW_MODE_PASSWORD_STAR) {
            mPaintContent.setColor(mTextColor);
            for (int ii = 0; ii < password.length(); ii++) {
                float startX = getDrawContentStartX(ii);
                canvas.drawText(mPaintContent, "*",
                        DecimalUtils.sub(DecimalUtils.div(TWO, DecimalUtils.div(TWO, mPaintContent.measureText("*"))),
                                startX),
                        getTextBaseline(mPaintContent, cy));
            }
        } else {
            mPaintContent.setColor(mTextColor);

            // 计算baseline
            float baselineText = getTextBaseline(mPaintContent, cy);
            int md = 0;
            for (int ii = 0; ii < password.length(); ii++) {
                float startX = getDrawContentStartX(ii);

                // 计算文字宽度
                String text = String.valueOf(password.charAt(ii));
                float textWidth = mPaintContent.measureText(text);

                // 没有焦点时
                if (!isFocused()) {
                    mPaintContent.setColor(mTextColorControlNoFocus);
                }

                // 绘制文字x应该还需要减去文字宽度的一半
                canvas.drawText(mPaintContent, text,
                        DecimalUtils.sub(DecimalUtils.div(TWO, textWidth), startX), baselineText);
                md = ii + 1;
            }
            for (; md < mHintText.length(); md++) {
                float startX = getDrawContentStartX(md);

                // 计算文字宽度
                String text = String.valueOf(mHintText.charAt(md));
                float textWidth = mPaintContent.measureText(text);
                canvas.drawText(mPaintHint, text,
                        DecimalUtils.sub(DecimalUtils.div(TWO, textWidth), startX), baselineText);
            }
        }
    }

    /**
     * 绘制光标
     * 光标只有一个,所以不需要根据循环来绘制,只需绘制第N个就行
     * 即:
     * 当输入内容长度为0,光标在第0个位置
     * 当输入内容长度为1,光标应在第1个位置
     * ...
     * 所以光标所在位置为输入内容的长度
     * 这里光标的长度默认就是 height/2
     *
     * @param canvas
     */
    private void drawCursor(Canvas canvas) {
        // 如果设置得有光标高度,那么startY = (高度-光标高度)/2+边框宽度
        if (mCursorHeight == 0) {
            mCursorHeight = getHeight() / TWO;
        }
        float alpha = mPaintCursor.getAlpha();
        if (mIsCursorFlag) {
            mPaintCursor.setAlpha(0f);
        } else {
            mPaintCursor.setAlpha(1f);
        }
        mIsCursorFlag = !mIsCursorFlag;
        LogUtil.d("alpha--drawCursr=" + alpha);
        int sy = (getHeight() - mCursorHeight) / TWO;
        String content = getText();
        float startX = getDrawContentStartX(content.length());
        float startY = DecimalUtils.add(sy, mOtpLineWidth);
        float stopY = DecimalUtils.sub(mOtpLineWidth, DecimalUtils.sub(sy, getHeight()));
        canvas.drawLine(new Point(startX, startY), new Point(startX, stopY), mPaintCursor);
    }

    /**
     * 计算三种输入框样式下绘制圆和文字的x坐标
     *
     * @param index 循环里面的下标 i
     * @return 文字的x坐标
     */
    private float getDrawContentStartX(int index) {
        switch (mInputBoxStyle) {
            case INPUT_BOX_STYLE_CONNECT:
            case INPUT_BOX_STYLE_SINGLE:
            case INPUT_BOX_STYLE_UNDERLINE:
                return DecimalUtils.addFloat(DecimalUtils.addFloat(
                        DecimalUtils.addFloat(DecimalUtils.div(TWO, getContentItemWidth()),
                                DecimalUtils.mul(index, getContentItemWidth())),
                        DecimalUtils.mul(index, mOtpItemSpacing)
                        ), DecimalUtils.mul(DecimalUtils.add(1, DecimalUtils.mul(TWO, index)), mOtpLineWidth)
                );
            default:
                return DecimalUtils.addFloat(DecimalUtils.addFloat(DecimalUtils.div(TWO,
                        getContentItemWidth()), DecimalUtils.mul(index, getContentItemWidth())),
                        DecimalUtils.mul(index, mDivisionLineSize));
        }
    }

    /**
     * 绘制下划线输入框样式
     * 线条起点startX:每个字符所占宽度itemWidth + 每个字符item之间的间距mSpaceSize
     * 线条终点stopX:stopX与startX之间就是一个itemWidth的宽度
     *
     * @param canvas 画布
     */
    private void drawUnderlineStyle(Canvas canvas) {
        String content = getText();
        for (int ii = 0; ii < mOtpItemCount; ii++) {
            // 计算绘制下划线的startX
            float startX = DecimalUtils.addFloat(DecimalUtils.mul(ii, getContentItemWidth()),
                    DecimalUtils.mul(ii, DecimalUtils.addFloat(mOtpItemSpacing, DecimalUtils.mul(TWO, mOtpLineWidth))));

            // stopX
            float stopX = DecimalUtils.add(DecimalUtils.add(getContentItemWidth(), startX), mOtpLineWidth);

            // 对于下划线这种样式,startY = stopY
            float startY = DecimalUtils.sub(DecimalUtils.div(TWO, mOtpLineWidth), getHeight());

            // 这里判断是否设置有输入框获取焦点时,下划线的颜色
            if (mUnderlineFocusColor != null) {
                LogUtil.d("drawUnderlineStyle---i=" + ii);
                if (content.length() > ii) {
                    mPaintUnderline.setColor(mUnderlineFullColor);
                } else {
                    LogUtil.d("drawUnderlineStyle else=" + ii);
                    mPaintUnderline.setColor(mUnderlineEmptyColor);
                }

                if (content.length() == ii) {
                    mPaintBorder.setColor(mOtpLineColorFocus);
                } else {
                    mPaintBorder.setColor(mOtpLineColorNoFocus);
                }

                mPaintBorder.setStyle(Paint.Style.STROKE_STYLE);
                int jj = ii;
                if (mOtpViewType == 1 && getText().length() != SIX) {
                    jj = ii - 1;
                }
                float left = DecimalUtils.addFloat(DecimalUtils.addFloat(DecimalUtils.addFloat(DecimalUtils.mul(jj,
                        getContentItemWidth()), DecimalUtils.mul(jj, mOtpItemSpacing)),
                        DecimalUtils.mul(DecimalUtils.mul(jj, mOtpLineWidth), TWO)),
                        DecimalUtils.div(TWO, mOtpLineWidth));

                float right = DecimalUtils.sub(DecimalUtils.div(TWO, mOtpLineWidth),
                        DecimalUtils.addFloat(DecimalUtils.addFloat(DecimalUtils.mul(jj, mOtpItemSpacing),
                                DecimalUtils.mul(jj + 1, getContentItemWidth())),
                        DecimalUtils.mul(DecimalUtils.mul(jj + 1, TWO), mOtpLineWidth)));

                LogUtil.d("drawUnderlineStyle---drawRoundRect()");
                mRectFloatSingleBox.modify(left, DecimalUtils.div(TWO, mOtpLineWidth), right,
                        DecimalUtils.sub(DecimalUtils.div(TWO, mOtpLineWidth), getHeight()));

                // 如果没有焦点
                if (!isFocused() && !Color.TRANSPARENT.equals(mBorderColorControlNoFocus)) {
                    mPaintBorder.setColor(mBorderColorControlNoFocus);
                }
                canvas.drawRoundRect(mRectFloatSingleBox, mCornerSize, mCornerSize, mPaintBorder);
            }

            // 如果没有焦点
            if (!isFocused() && mBackgroudNoFocus.getValue() != Color.TRANSPARENT.getValue()) {
                mPaintUnderline.setColor(mBackgroudNoFocus);
            }

            canvas.drawLine(new Point(DecimalUtils.addFloat(startX, DecimalUtils.div(TWO, mOtpLineWidth)), startY),
                    new Point(DecimalUtils.addFloat(stopX,
                            DecimalUtils.div(TWO, mOtpLineWidth)), startY),
                    mPaintUnderline);
        }
    }

    private void drawBackgroundRect(Canvas canvas) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL_STYLE);
        paint.setStrokeWidth(mOtpLineWidth);
        for (int ii = 0; ii < mOtpItemCount; ii++) {
            String content = getText();
            if (content.length() == ii) {
                paint.setColor(mItemBackgroudFocus);
            } else {
                paint.setColor(mItemBackgroudNoFocus);
            }
            float left = DecimalUtils.addFloat(DecimalUtils.div(TWO, mOtpLineWidth),
                    DecimalUtils.addFloat(DecimalUtils.addFloat(DecimalUtils.mul(ii, getContentItemWidth()),
                            DecimalUtils.mul(ii, mOtpItemSpacing)),
                    DecimalUtils.mul(DecimalUtils.mul(ii, mOtpLineWidth), TWO)));

            float right = DecimalUtils.sub(DecimalUtils.div(TWO, mOtpLineWidth),
                    DecimalUtils.addFloat(DecimalUtils.addFloat(DecimalUtils.mul(ii, mOtpItemSpacing),
                            DecimalUtils.mul(ii + 1, getContentItemWidth())),
                            DecimalUtils.mul(DecimalUtils.mul(ii + 1, TWO), mOtpLineWidth)));

            mRectFloatSingleBox.modify(left, DecimalUtils.div(TWO, mOtpLineWidth), right,
                    DecimalUtils.sub(DecimalUtils.div(TWO, mOtpLineWidth), getHeight()));

            // 如果没有焦点
            if (!isFocused() && mBackgroudNoFocus.getValue() != Color.TRANSPARENT.getValue()) {
                paint.setColor(mBackgroudNoFocus);
            }
            canvas.drawRoundRect(mRectFloatSingleBox, mCornerSize, mCornerSize, paint);

            if (mItemBackgroudImg != null) {
                // 绘制图片
                Image image = new Image(getContext());
                image.setImageElement(mItemBackgroudImg);
                PixelMap pixelmap = image.getPixelMap();
                canvas.drawPixelMapHolderRect(new PixelMapHolder(pixelmap), mRectFloatSingleBox, new Paint());
            }
        }
    }

    private void drawSingleStyle(Canvas canvas) {
        for (int ii = 0; ii < mOtpItemCount; ii++) {
            mRectFloatSingleBox.clear();
            float left = DecimalUtils.addFloat(DecimalUtils.addFloat(
                    DecimalUtils.addFloat(DecimalUtils.mul(ii, getContentItemWidth()),
                    DecimalUtils.mul(ii, mOtpItemSpacing)),
                    DecimalUtils.mul(DecimalUtils.mul(ii, mOtpLineWidth), TWO)),
                    DecimalUtils.div(TWO, mOtpLineWidth));

            float right = DecimalUtils.sub(DecimalUtils.div(TWO, mOtpLineWidth),
                    DecimalUtils.addFloat(DecimalUtils.addFloat(DecimalUtils.mul(ii, mOtpItemSpacing),
                            DecimalUtils.mul(ii + 1, getContentItemWidth())),
                            DecimalUtils.mul(DecimalUtils.mul(ii + 1, TWO), mOtpLineWidth)));

            // 为避免在onDraw里面创建RectF对象,这里使用rectF.set()方法
            mRectFloatSingleBox.modify(left, DecimalUtils.div(TWO, mOtpLineWidth), right,
                    DecimalUtils.sub(DecimalUtils.div(TWO, mOtpLineWidth), getHeight()));
            LogUtil.d("drawSingleStyle---i=" + ii);
            canvas.drawRoundRect(mRectFloatSingleBox, mCornerSize, mCornerSize, mPaintBorder);
        }
    }

    private void drawConnectStyle(Canvas canvas) {
        LogUtil.d("drawConnectStyle---drawRoundRect()");

        // 每次重新绘制时,先将rectF重置下
        mRectFloatConnect.clear();

        // 需要减去边框的一半
        mRectFloatConnect.modify(
                DecimalUtils.div(TWO, mOtpLineWidth),
                DecimalUtils.div(TWO, mOtpLineWidth),
                DecimalUtils.sub(DecimalUtils.div(TWO, mOtpLineWidth), getWidth()),
                DecimalUtils.sub(DecimalUtils.div(TWO, mOtpLineWidth), getHeight())
        );
        canvas.drawRoundRect(mRectFloatConnect, mCornerSize, mCornerSize, mPaintBorder);

        // 绘制分割线
        drawDivisionLine(canvas);
    }

    private void drawDivisionLine(Canvas canvas) {
        float stopY = DecimalUtils.sub(mOtpLineWidth, getHeight());
        for (int ii = 0; ii < mOtpItemCount - 1; ii++) {
            // 对于分割线条,startX = stopX
            float startX = DecimalUtils.addFloat(DecimalUtils.addFloat(DecimalUtils.mul(ii + 1,
                    getContentItemWidth()), DecimalUtils.mul(ii, mDivisionLineSize)),
                    DecimalUtils.addFloat(mOtpLineWidth, DecimalUtils.div(TWO, mDivisionLineSize)));

            canvas.drawLine(new Point(startX, mOtpLineWidth), new Point(startX, stopY), mPaintDivisionLine);
        }
    }

    /**
     * 计算3种样式下,相应每个字符item的宽度
     *
     * @return 每个字符item的宽度
     */
    private float getContentItemWidth() {
        // 计算每个密码字符所占的宽度,每种输入框样式下,每个字符item所占宽度也不一样
        float tempWidth;
        switch (mInputBoxStyle) {
            case INPUT_BOX_STYLE_SINGLE:
            case INPUT_BOX_STYLE_UNDERLINE:
            case INPUT_BOX_STYLE_CONNECT:

                // 单个输入框样式：宽度-间距宽度(字符数-1) * 每个间距宽度-每个输入框的左右边框宽度
                tempWidth = DecimalUtils.sub(DecimalUtils.mul(DecimalUtils.mul(TWO, mOtpItemCount), mOtpLineWidth),
                        DecimalUtils.sub(DecimalUtils.mul(mOtpItemCount - 1, mOtpItemSpacing), getWidth()));
                break;
            default:

                tempWidth = DecimalUtils.sub(DecimalUtils.mul(TWO, mOtpLineWidth),
                        DecimalUtils.sub(DecimalUtils.mul(mDivisionLineSize, mOtpItemCount - 1), getWidth()));
                break;
        }
        return tempWidth / mOtpItemCount;
    }

    /**
     * 根据view的测量宽度,计算每个item的宽度
     *
     * @param measureWidth view的measure
     * @return onMeasure时的每个item宽度
     */
    private float getContentItemWidthOnMeasure(int measureWidth) {
        // 计算每个密码字符所占的宽度,每种输入框样式下,每个字符item所占宽度也不一样
        float tempWidth;
        switch (mInputBoxStyle) {
            case INPUT_BOX_STYLE_SINGLE:

                // 单个输入框样式：宽度-间距宽度(字符数-1) * 每个间距宽度-每个输入框的左右边框宽度
                tempWidth = DecimalUtils.sub(DecimalUtils.mul(mOtpLineWidth, DecimalUtils.mul(TWO, mOtpItemCount)),
                        DecimalUtils.sub(DecimalUtils.mul(mOtpItemCount - 1, mOtpItemSpacing), measureWidth));
                break;
            case INPUT_BOX_STYLE_UNDERLINE:

                // 下划线样式：宽度-间距宽度(字符数-1) * 每个间距宽度
                tempWidth = DecimalUtils.sub(DecimalUtils.mul(mOtpItemCount - 1, mOtpItemSpacing), measureWidth);
                break;
            case INPUT_BOX_STYLE_CONNECT:

                // 矩形输入框样式：宽度-左右两边框宽度-分割线宽度(字符数-1) * 每个分割线宽度
            default:
                tempWidth = DecimalUtils.sub(DecimalUtils.mul(TWO, mOtpLineWidth),
                        DecimalUtils.sub(DecimalUtils.mul(mDivisionLineSize, mOtpItemCount - 1), measureWidth));
                break;
        }
        return tempWidth / mOtpItemCount;
    }

    /**
     * 计算绘制文本的基线
     *
     * @param paint 绘制文字的画笔
     * @param halfHeight 高度的一半
     * @return 文本的基线
     */
    private float getTextBaseline(Paint paint, float halfHeight) {
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        float dy = DecimalUtils.sub(fontMetrics.bottom,
                DecimalUtils.div(TWO, DecimalUtils.sub(fontMetrics.top, fontMetrics.bottom)));
        return DecimalUtils.addFloat(halfHeight, dy);
    }

    /**
     * 设置密码是否可见
     *
     * @param mode
     * @throws IllegalArgumentException
     */
    public void setContentShowMode(int mode) {
        if (mode != CONTENT_SHOW_MODE_PASSWORD && mode != CONTENT_SHOW_MODE_TEXT) {
            throw new IllegalArgumentException(
                    "the value of the parameter must be one of"
                            + "{1:EDIT_SHOW_MODE_PASSWORD} or "
                            + "{2:EDIT_SHOW_MODE_TEXT}"
            );
        }
        mOptContentShowMode = mode;
        invalidate();
    }

    /**
     * 获取密码显示模式
     *
     * @return 密码显示模式
     */
    public int getContentShowMode() {
        return mOptContentShowMode;
    }

    /**
     * 设置输入框样式
     *
     * @param inputBoxStyle
     * @throws IllegalArgumentException 参数不合法
     */
    public void setInputBoxStyle(int inputBoxStyle) {
        if (inputBoxStyle != INPUT_BOX_STYLE_CONNECT
                && inputBoxStyle != INPUT_BOX_STYLE_SINGLE
                && inputBoxStyle != INPUT_BOX_STYLE_UNDERLINE
        ) {
            throw new IllegalArgumentException(
                    "the value of the parameter must be one of"
                            + "{1:INPUT_BOX_STYLE_CONNECT}, "
                            + "{2:INPUT_BOX_STYLE_SINGLE} or "
                            + "{3:INPUT_BOX_STYLE_UNDERLINE}"
            );
        }
        mInputBoxStyle = inputBoxStyle;

        // 这里没有调用invalidate因为会存在问题
        // invalidate会重绘,但是不会去重新测量,当输入框样式切换的之后,item的宽度其实是有变化的,所以此时需要重新测量
        // requestLayout,调用onMeasure和onLayout,不一定会调用onDraw,当view的l,t,r,b发生改变时会调用onDraw
        postLayout();
    }

    /**
     * 1：风格为line1；2：风格为rectangle;
     *
     * @param viewType
     */
    public void setmOtpViewType(int viewType) {
        mOtpViewType = viewType;
        postLayout();
    }

    /**
     * 获取输入框样式
     *
     * @return 输入框样式
     */
    public int getInputBoxStyle() {
        return mInputBoxStyle;
    }

    /**
     * 动画
     *
     * @since 2021-04-26
     */
    private class CursorAnimEventHandler extends EventHandler {
        CursorAnimEventHandler(EventRunner runner) throws IllegalArgumentException {
            super(runner);
        }

        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            if (event == null) {
                return;
            }
            if (event.eventId == ONE_HUNDRED_AND_TEN) {
                float alpha = mPaintCursor.getAlpha();
                mPaintCursor.setAlpha(alpha == 0f ? 1f : 0f);
                invalidate();
                LogUtil.d("showCursor---alpha=" + alpha);
            }
            LogUtil.d("showCursor---事件Id" + event.eventId);
        }

        /**
         * 分发事件
         *
         * @param event
         */
        @Override
        public void distributeEvent(InnerEvent event) {
            super.distributeEvent(event);
            LogUtil.d("Runnable的eventId:" + event.eventId);
        }
    }

    private void startCursorAnim() {
        LogUtil.d("showCursor");

        // 获取当前主线程的EventRunner，用来存放事件队列，用EventRunner其他方法会报错
        EventRunner eventRunner = EventRunner.current();
        if (eventRunner == null) {
            LogUtil.d("showCursor---当前线程未获取到");
            return;
        }
        LogUtil.d("showCursor---eventRunner对应的线程Id：" + eventRunner.getThreadId());

        // handler必须绑定eventrunner
        CursorAnimEventHandler handler = new CursorAnimEventHandler(eventRunner);
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    handler.sendEvent(InnerEvent.get(ONE_HUNDRED_AND_TEN));
                    try {
                        Thread.sleep(FIVE_HUNDRED);
                    } catch (InterruptedException e) {
                        LogUtil.e("interrupted error!");
                    }
                }
            }
        }).start();
    }
}
